#include"my_msg.pb.h"
#include<sqlite3.h>
#include<iostream>
#include<string>
#include<unordered_map>
#include<mutex>
#include<sys/types.h>
#include<sys/stat.h>
#include<memory>
#include<sstream>
#include<gtest/gtest.h>

//交换机数据管理,需要实现3个类，交换机类、交换机数据持久类，交换机数据管理类

namespace zjx
{
    class SetHelper
    {
    public:
        static size_t split(const std::string& str , const std::string& sep , std::vector<std::string> &result)
        {
            size_t pos = 0, index = 0;
            while(index < str.size())
            {
                pos = str.find(sep);
                if(pos == std::string::npos)
                {
                    //从查找到结尾的字符串放入数组中并返回
                    result.push_back(str.substr(index));
                    return result.size();
                }
                //如果pos  与 index 相等，则代表两个分隔符之间没有数据，此时continue
                if(pos == index) 
                {
                    index += sep.size();
                    continue;
                }
                //此时pos 合法，直接截取就可以了
                result.push_back(str.substr(index ,pos-index));
                index = pos + sep.size();//跳过分隔符
            }
            //分割完了
            return result.size();
        }
    };

    //sqlite工具类的实现,对sqlite3进行封装
    class SqliteHelper
    {
    public:
        //函数指针，exec 中回调函数的函数指针，当使用select 需要对得到数据做处理的时候就需要使用sqlite3_exec 的回调函数
        typedef int(*SqliteCallback)(void*, int , char** , char**);
        SqliteHelper(const std::string& dbfile):_dbfile(dbfile),_handler(nullptr){}
        //打开、执行、关闭
        bool open(int safe_level = SQLITE_OPEN_FULLMUTEX)//需要传入sqlite数据库的安全等级，默认为串行：SQLITE_OPEN_FULLMUTEX
        {
            //需要调用sqlite3_open_v2 来打开数据库文件，成功返回SQLITE_OK
            int ret = sqlite3_open_v2(_dbfile.c_str() , &_handler ,safe_level| SQLITE_OPEN_READWRITE |SQLITE_OPEN_CREATE , nullptr );
            if(ret != SQLITE_OK)
            {
                std::cerr << "打开数据库文件失败" << std::endl;
                return false;
            }
            return true;
        }
        bool exec(const std::string &sql , SqliteCallback cb , void* args)
        {
            //sqlite3_exec 成功返回SQLITE_OK
            int ret  =sqlite3_exec(_handler,sql.c_str() , cb , args , nullptr);
            if(ret != SQLITE_OK)
            {
                std::cerr << "执行sql 语句失败" << std::endl;
                return false;
            }
            return true;
        }
        void close()
        {
            //调用sqlite3_close_v2
            if(_handler) sqlite3_close_v2(_handler);
        }
    private:
        std::string _dbfile;//数据库名称
        sqlite3 * _handler;//数据库句柄
    };

    //文件操作类
    class FileHelper
    {
    public:
        //创建目录
        static bool createDirectory(const std::string& path)
        {
            //截取字符串，一级一级地创建;如果目录存在而调用mkdir 函数创建目录errno 为EEXIST
            int pos = 0, index = 0;
            while(index < path.size())
            {
                pos = path.find("/" , index);
                //如果没有找到，就说明这是一个目录或者这是最后一个目录
                if(pos == std::string::npos)
                {
                    return mkdir(path.c_str() , 0775)==0;
                }
                //获取当前层的目录
                std::string subpath = path.substr(0, pos);
                int ret = mkdir(subpath.c_str() , 0775);
                if(ret != 0 && errno!=EEXIST)
                {
                    std::cerr << "创建目录失败" << std::endl;
                    return;
                }
                //移动index
                index = pos + 1;
            }
            return true;
        }
        static std::string parentDirectory(const std::string& filename)//获取文件的目录。不存在就创建
        {
            //需要借助于rfind
            size_t pos = filename.rfind("/");
            if(pos == std::string::npos)
            {
                //没有找到，就是子当前目录下
                return "./";
            }
            //找到了就获取目录
            return filename.substr(0 , pos);
        }
    };
    //定义交换机类
    struct Exchange
    {
        using ptr = std::shared_ptr<Exchange>;//定义指向交换机类的指针
        //交换机的名称 类型 持久化标志 是否自动删除标志 其他参数
        std::string name;
        ExchangeType type;
        bool durable;
        bool auto_delete;
        google::protobuf::Map<std::string , std::string> args;

        Exchange(){}
        Exchange(const std::string& ename , ExchangeType etype , bool edurable , bool eauto_delete
        , const google::protobuf::Map<std::string , std::string>& eargs)
        :name(ename),type(etype),durable(edurable),auto_delete(eauto_delete),args(eargs){}

        //在args 当中存放键值对，在存储数据的时候会将数据以 key=val&kay=val 的形式组织，内部解析str+args 字符串，将内容存储在成员当中
        void SetArgs(const std::string& str_args)
        {
            //key=val&kay=val
            //现将字符串按照 & 进行分割，放在数组中
            std::vector<std::string> sub_args;
            SetHelper::split(str_args , "&" , sub_args);
            //再根据数组当中的内容按照符号 = 进行分割得到kv 放入args 当中
            for(auto & str : sub_args)
            {
                size_t pos = str.find("=");
                std::string key = str.substr(0, pos);
                std::string value = str.substr(pos + 1);
                args[key] = value;
            }
        }

        //将args当中的数据序列化之后返回一个字符串
        std::string getArgs()
        {
            std::string result;
            for(auto start = args.begin() ; start != args.end();start++)
            {
                result += (start->first + "=" + start->second + "&"); 
            }
            return result;
        }
    };

    using ExchangeMap = std::unordered_map<std::string , Exchange::ptr>;
    //交换机数据持久化管理的类
    class ExchangeMapper
    {
    public:
        //构造
        ExchangeMapper(const std::string& dbfile):_sql_helper(dbfile)
        {
            //数据库在参数列表的时候不存在会自动去创建，此处需要保证该数据库文件所处的目录是存在的
            std::string path = FileHelper::parentDirectory(dbfile);
            FileHelper::createDirectory(path);
            //打开数据库文件
            _sql_helper.open();
            //创建管理交换机的数据库表
            createTable();
        }
        ~ExchangeMapper(){}
        //创建管理交换机的表
        void createTable()
        {   
            //书写sql
            #define CREATE_TABLE "create table if not exists exchange_table(\
                                name varchar(32) primary key, \
                                type int, \
                                durable int, \
                                auto_delete bool ,\
                                args varchar(128));"
            //调用sqplite 中的sqlite_exec
            bool ret= _sql_helper.exec(CREATE_TABLE , nullptr , nullptr);
            if(ret == false)
            {
                std::cerr << "创建交换机数据库失败";
                return;
            }
        }
        //移除管理交换机的表
        void removeTable()
        {
            //书写sql + 调用sqlite_exec
            #define DROP_TABLE "drpo table if exists exchange_table;"
           bool ret =  _sql_helper.exec(DROP_TABLE , nullptr, nullptr);
            if(ret == false)
            {
                std::cerr << "删除交换机数据库失败";
                return;
            }
        }
        //向管理交换机表中插入新的交换机
        bool insert(Exchange::ptr& exp)
        {
            //书写sql + 调用sqlite_exec
            //交换机的名称 类型 持久化标志 是否自动删除标志 其他参数
            std::stringstream ss;
            ss << "insert into exchange_table values(";
            ss << "'" << exp->name << "',";
            ss << "'" << exp->type << "',";
            ss << "'" << exp->durable << "',";
            ss << "'" << exp->auto_delete << "',";
            ss << "'" << exp->getArgs() << "');";
            return _sql_helper.exec(ss.str() , nullptr, nullptr);
        }
        //从交换机表中移除对应名称的交换机
        bool remove(const std::string& name)
        {
            std::stringstream ss;
            ss << "delete from exchange_table where name=" ;
            ss << "'" << name << "';";
            return _sql_helper.exec(ss.str() , nullptr , nullptr); 
        }
        //欢呼历史数据，其实就是将存放在数据库中的数据拿出来放在内存中，函数recovery 返回拿到的数据
        // using ExchangeMap = std::unordered_map<std::string , Exchange::ptr>; 交换机与数据库的对应关系
        ExchangeMap recovery()
        {
            //本质上就是执行select sql语句
            ExchangeMap result;
            std::string sql = "select name,type,durable,auto_delete , args from exchange_table;";
            _sql_helper.exec(sql, selectCallback , &result);
            return result;
        }
    private:
        //执行sql语句，select 调用sqlite_exec 所要执行的回调函数
        static int selectCallback(void* arg, int numcol , char** row , char** fileds)
        {
            //对arg进行强转，数据保存在row 中，需要依靠arg 带出去
            ExchangeMap * result = (ExchangeMap*)arg;
            //创建一个Exchange 对象用来存放数据
            auto exp = std::shared_ptr<Exchange>();
            //遍历row ，row 当中存放了每一列的数据信息
            exp->name = row[0];
            exp->type = (zjx::ExchangeType)std::stoi(row[1]);
            exp->durable = (bool)std::stoi(row[2]);
            exp->auto_delete = (bool) std::stoi(row[3]);
            //最后一个参数可以不存在，需要判断一下
            if(row[4]) exp->SetArgs(row[4]);
            //将exp 这个表放入result 当中，相当于就是放入了args 中
            result->insert(std::make_pair(exp->name , exp));
            return 0;//需要注意的是此处必须返回0，不会回调用ABORT导致程序终止
        }
    private:
        //因为是针对数据库的操作，故而此处的成员变量为sqlite的操作句柄
        SqliteHelper _sql_helper;
    };

    class ExchangeManager//交换机内存管理类
    {
    public: 
        using ptr = std::shared_ptr<ExchangeManager>;
        ExchangeManager(const std::string& dbfile)
        :_mapper(dbfile)//数据库
        {
            //回复历史数据
            _exchanges = _mapper.recovery();
        }
        ~ExchangeManager(){}
        //声明交换机
        //交换机的名称 类型 持久化标志 是否自动删除标志 其他参数
        bool declareExchange(const std::string& name, ExchangeType type , bool duriable , bool auto_delete
        , const google::protobuf::Map<std::string , std::string>& args)
        {
            //先加锁，然后去_exchanges 中查找
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _exchanges.find(name);
            if(it != _exchanges.end())
            {
                return true;//找到了
            }
            //没有找到那么就创建这个表 
            auto exp = std::make_shared<Exchange>(name , type , duriable , auto_delete, args);
            //判断是否持久化
            if(duriable == true)
            {
                bool ret = _mapper.insert(exp);//向数据库中插入这张表
                if(ret == false) return false;
            }
            //向管理交换机信息的表中进行写入
            _exchanges.insert(std::make_pair(name ,exp));
            return true;
        }
        //删除交换机
        void deleteExchange(const std::string& name)
        {
            //加锁，在管理交换机的数据表(内存) _exchanges中查找，没有则返回，有则(持久化存储的话)将其从_exchange 中删除并且处理这个数据库表
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _exchanges.find(name);
            if(it == _exchanges.end())
            {
                return;
            }
            if(it->second->durable == true) _mapper.remove(name);
            _exchanges.erase(it);
        }
        //获取指定交换机对象
        //using ptr = std::shared_ptr<Exchange>;
        Exchange::ptr selectExchange(const std::string& name)
        {
            //加锁+ 查找
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _exchanges.find(name);
            if(it == _exchanges.end())
            {
                return Exchange::ptr();//返回一个空对象
            }
            //返回这个交换机所对应的数据库
            return it->second;
        }
        //判断交换机是否存在
        bool exists(const std::string& name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _exchanges.find(name);
            if(it == _exchanges.end())
            {
                return false;
            }
            return true;
        }
        size_t size()//获取所有交换机的数量
        {
            //加锁
            std::unique_lock<std::mutex> lock(_mutex);
            return _exchanges.size();
        }
        //清理所有的交换机数据，也就是清理表exchange_table 中的数据，如果设置了持久化，还需要清理对应的数据库文件
        void clear()
        {
            //会访问临界资源，加锁
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeTable();
            _exchanges.clear();
        }
    private:
        //哈希表 为临界资源需要加锁保护
        std::mutex _mutex;
        ExchangeMapper _mapper;
        ExchangeMap _exchanges;//临界资源
        //using ExchangeMap = std::unordered_map<std::string , Exchange::ptr>; 交换机与数据库的对应关系
    };
}

//全局指向所有交换机管理对象
zjx::ExchangeManager::ptr emp;
//单元测试
class ExchangeTest : public testing::Environment
{
public:
    //需要我们重写SetPu 与 TearDown
    virtual void SetUp() override
    {   
        //初始化
        emp = std::make_shared<zjx::ExchangeManager>("./data/meta.db");

    }
    virtual void TearDown() override
    {
        std::cout << "最后的清理" << std::endl;
    }
};

TEST(exchange_test , insert_test)
{
    std::unordered_map<std::string , std::string> Map = {"k1" ,"v1"};
    //交换机的名称 类型 持久化标志 是否自动删除标志 其他参数
    emp->declareExchange("exchange1" , zjx::ExchangeType::DIRECT , true, false , Map);
    emp->declareExchange("exchange2" , zjx::ExchangeType::DIRECT , true, false , Map);
    emp->declareExchange("exchange3" , zjx::ExchangeType::DIRECT , true, false , Map);
    emp->declareExchange("exchange4" , zjx::ExchangeType::DIRECT , true, false , Map);
    ASSERT_EQ(emp->size() , 4);
}

int main(int argc , char* argv[])
{
    testing::InitGoogleTest(&argc , argv);
    testing::AddGlobalTestEnvironment(new ExchangeTest);
    int rte = RUN_ALL_TESTS();
    return 0;
}